#include "src/scene/camera.h"

#include <cmath>
#include <stdlib.h>

#include "src/math/vector.h"

#include "src/defines/antialiasingType.h"
#include "src/defines/filterType.h"

//#define TRAN 1

#define TESTANDO 1

Camera::Camera(){
    #if TESTANDO
    eye = Vector3D(0, -7, 6);
    lookAt = Vector3D(0, 0, 0);
    up = Vector3D(0, 0, 1);

    calculateLocalCoordinates();

    mPerPixel = 1;
    nPerPixel = 1;
    antialiasingType = NO_ANTIALIASING;
    filterType = MEAN;
    filterGaussianSigma = 1;
    filterLaplacianNeighborhood = LAPLACIAN_NEGATIVE;
    filterLaplacianNegative = LAPLACIAN_NEIGHBORHOOD_8;

    depthOfField = false;

    srand ( time(NULL) );

    m = 300;
    n = 250;
    focalDistance = 9.0;
    imagePlane = -9.0;//deixando negativo logo!
    zoom = 80;
    radius = 0.5;

    #else //TESTANDO
    eye = Vector3D(0, -13, 0);
    lookAt = Vector3D(0, 0, 0);
    up = Vector3D(0, 0, 1);

    calculateLocalCoordinates();

    mPerPixel = 5;
    nPerPixel = 5;
    antialiasingType = REGULAR;
    filterType = MEAN;
    filterGaussianSigma = 1;
    filterLaplacianNeighborhood = LAPLACIAN_NEGATIVE;
    filterLaplacianNegative = LAPLACIAN_NEIGHBORHOOD_8;

    depthOfField = true;

    srand ( time(NULL) );

    m = 400;
    n = 300;
    focalDistance = 13.0;
    imagePlane = -13.0;//deixando negativo logo!
    zoom = 100;
    radius = 0.5;
    #endif //TESTANDO

    setLeftRightBottomTop();
}

Camera::Camera(const Camera &_c)
{
    *this = _c;
}

Camera::Camera(unsigned int _m, unsigned int _n, double _f, double _i)
    : m(_m), n(_n), focalDistance(_f), imagePlane(-_i){
    eye = Vector3D(0, -15, 0);
    lookAt = Vector3D(0, 0, 0);
    up = Vector3D(0, 0, 1);

    zoom = 100;
    radius = 0.5;
    calculateLocalCoordinates();
    setLeftRightBottomTop();

    depthOfField = true;

    mPerPixel = 5;
    nPerPixel = 5;
    antialiasingType = REGULAR;
    filterType = MEAN;
    filterGaussianSigma = 1;
    filterLaplacianNeighborhood = LAPLACIAN_NEGATIVE;
    filterLaplacianNegative = LAPLACIAN_NEIGHBORHOOD_8;

    srand ( time(NULL) );
}

Camera::Camera(const Vector3D& _eye, const Vector3D& _lookAt, const Vector3D& _up, unsigned int _m, unsigned int _n, double _f, double _i)
    : eye(_eye), lookAt(_lookAt), up(_up), m(_m), n(_n), focalDistance(_f), imagePlane(-_i){
    zoom = 100;
    radius = 0.5;
    setLeftRightBottomTop();
    calculateLocalCoordinates();

    depthOfField = true;

    mPerPixel = 5;
    nPerPixel = 5;
    antialiasingType = REGULAR;
    filterType = MEAN;
    filterGaussianSigma = 1;
    filterLaplacianNeighborhood = LAPLACIAN_NEGATIVE;
    filterLaplacianNegative = LAPLACIAN_NEIGHBORHOOD_8;

    srand ( time(NULL) );
}

Camera::~Camera(){
}

Camera &Camera::operator=(const Camera &_c)
{
    eye = _c.eye;
    lookAt = _c.lookAt;
    up = _c.up;

    u = _c.u;
    v = _c.v;
    w = _c.w;

    m = _c.m;
    n = _c.n;
    mPerPixel = _c.mPerPixel;
    nPerPixel = _c.nPerPixel;

    depthOfField = _c.depthOfField;
    focalDistance = _c.focalDistance;
    imagePlane = _c.imagePlane;
    left = _c.left;
    right = _c.right;
    top = _c.top;
    bottom = _c.bottom;
    left_ = _c.left_;
    right_ = _c.right_;
    top_ = _c.top_;
    bottom_ = _c.bottom_;
    zoom = _c.zoom;
    radius = _c.radius;

    antialiasingType = _c.antialiasingType;
    filterType = _c.filterType;
    filterGaussianSigma = _c.filterGaussianSigma;
    filterLaplacianNeighborhood = _c.filterLaplacianNeighborhood;
    filterLaplacianNegative = _c.filterLaplacianNegative;

    transform = _c.transform;

    srand ( time(NULL) );

    return *this;
}

void Camera::setLeftRightBottomTop()
{
    double d = 2*zoom;
    double m_ = (double)m / d;
    double n_ = (double)n / d;

    left = m_;
    right = -m_;
    top = n_;
    bottom = -n_;

    top_ = (focalDistance * n_)/(-imagePlane);
    bottom_ = (focalDistance * n_)/(imagePlane);

    left_ = (focalDistance * m_)/(-imagePlane);
    right_ = (focalDistance * m_)/(imagePlane);
}

Ray Camera::getRay(unsigned int _i, unsigned int _j, unsigned int _k, unsigned int _l) const
{
    Vector3D e = eye, d;


    double us = 0, vs = 0;

    switch(antialiasingType)
    {
        case NO_ANTIALIASING:

//                if((_i == 0) && (_j == 0) && (_k == 0) && (_l == 0)) std::cout <<"sem anti [" <<  mPerPixel << ", " << nPerPixel << "]" << std::endl;
            us = (right + (left - right) * ( (_i + 0.5)/m));
            vs = (top +   (bottom - top) * ( (_j + 0.5)/n));
            break;

        case REGULAR:
//                if((_i == 0) && (_j == 0) && (_k == 0) && (_l == 0)) std::cout <<"reg [" <<  mPerPixel << ", " << nPerPixel << "]" << std::endl;
            us = (right + (left - right) * ( (_i + ((_k + 0.5)/mPerPixel))/m));
            vs = (top +   (bottom - top) * ( (_j + ((_l + 0.5)/nPerPixel))/n));
            break;

        case RANDOM:
        {
//                if((_i == 0) && (_j == 0) && (_k == 0) && (_l == 0)) std::cout <<"ram [" <<  mPerPixel << ", " << nPerPixel << "]" << std::endl;
            double xi1 = (rand()%100)/100.0;//numeros aleatorios entre [0, 1)
            double xi2 = (rand()%100)/100.0;//numeros aleatorios entre [0, 1)

            us = (right + (left - right) * ( (_i + xi1)/m));
            vs = (top +   (bottom - top) * ( (_j + xi2)/n));
            break;
        }
        case JITTERING:
        {
//                if((_i == 0) && (_j == 0) && (_k == 0) && (_l == 0)) std::cout <<"jitt [" <<  mPerPixel << ", " << nPerPixel << "]" << std::endl;
            double xi1 = (rand()%100)/100.0;//numeros aleatorios entre [0, 1)
            double xi2 = (rand()%100)/100.0;//numeros aleatorios entre [0, 1)

            us = (right + (left - right) * ( (_i + ((_k + xi1)/mPerPixel))/m));
            vs = (top +   (bottom - top) * ( (_j + ((_l + xi2)/nPerPixel))/n));
            break;
        }
    }

    d = (u * us) + (v * vs) + (w * imagePlane);

    if(depthOfField)
    {
        Vector3D pointAimed = eye + (d.normalize() * (focalDistance));

        double xi1 = (rand()%100)/100.0;//numeros aleatorios entre [0, 1)
        double xi2 = (rand()%100)/100.0;//numeros aleatorios entre [0, 1)

        e = eye + (u * (radius*(xi1 - 0.5))) + (v * (radius*(xi2 - 0.5)));

        d = pointAimed - e;
    }

    return Ray(e, d.normalize());
}

void Camera::calculateLocalCoordinates(){
    w = (eye - lookAt).normalize();

    u = (up.crossProduct(w).normalize());

    v = w.crossProduct(u);

    setMatrices();
}

void Camera::setMatrices(){
    Matrix localToWorld = Matrix(4,4);

    for(int i = 0; i < 3; i++){
        localToWorld[i][0] = u[i];
        localToWorld[i][1] = v[i];
        localToWorld[i][2] = w[i];
        localToWorld[i][3] = eye[i];
    }
    localToWorld[3][3] = 1;


    Matrix worldToLocal = localToWorld.transpose();
    for(int i = 0; i < 3; i++){
        worldToLocal[3][i] = 0;
    }
    worldToLocal[0][3] = -u.dotProduct(eye);
    worldToLocal[1][3] = -v.dotProduct(eye);
    worldToLocal[2][3] = -w.dotProduct(eye);

    transform.setLocalToWorld(localToWorld);
    transform.setWorldToLocal(worldToLocal);
}

void Camera::draw() const{
//    std::cout <<"Desenhando camera" << std::endl;
    glEnable(GL_COLOR_MATERIAL);
    glLineWidth(2);

    glDisable(GL_LIGHTING);
    glBegin(GL_LINES);
        glColor3f(1.0, 0.0, 0.0);
        //eixo X
        glVertex3d( eye.x, eye.y, eye.z);
        glVertex3d( eye.x + u.x, eye.y + u.y, eye.z + u.z);

        glColor3f(0.0, 1.0, 0.0);
        //eixo y
        glVertex3d( eye.x, eye.y, eye.z);
        glVertex3d( eye.x + v.x, eye.y + v.y, eye.z + v.z);

        glColor3f(0.0, 0.0, 1.0);
        //eixo z
        glVertex3d( eye.x, eye.y, eye.z);
        glVertex3d( eye.x + w.x, eye.y + w.y, eye.z + w.z);
    glEnd();
    glEnable(GL_LIGHTING);

    glPushMatrix();
    glMultMatrixd(transform.getLocalToWorld().toGlMatrix());
//
    Vector3D p0_(right_, top_, -focalDistance);
    Vector3D p1_(left_, top_, -focalDistance);
    Vector3D p2_(left_, bottom_, -focalDistance);
    Vector3D p3_(right_, bottom_, -focalDistance);
    Vector3D p0(right, top, imagePlane);
    Vector3D p1(left, top, imagePlane);
    Vector3D p2(left, bottom, imagePlane);
    Vector3D p3(right, bottom, imagePlane);


    glDisable(GL_LIGHTING);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glDisable(GL_CULL_FACE);


    glColor3f(1.0, 0.0, 0.0);

    glBegin(GL_QUADS);
        glVertex3d(p0.x, p0.y, p0.z);
        glVertex3d(p1.x, p1.y, p1.z);
        glVertex3d(p2.x, p2.y, p2.z);
        glVertex3d(p3.x, p3.y, p3.z);
    glEnd();

    if(depthOfField)
    {
        glColor3f(1.0, 1.0, 1.0);

        glBegin(GL_QUADS);
            glVertex3d(p0_.x, p0_.y, p0_.z);
            glVertex3d(p1_.x, p1_.y, p1_.z);
            glVertex3d(p2_.x, p2_.y, p2_.z);
            glVertex3d(p3_.x, p3_.y, p3_.z);
        glEnd();
    }

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glEnable(GL_LIGHTING);


    glLineWidth(1);
    glDepthMask(GL_FALSE);


    Vector3D n = (p0 - p1).crossProduct(p2 - p1).normalize();


    if(depthOfField)
    {
        glColor4f(0.2, 1.0, 1.0, 0.8);
        glBegin(GL_QUADS);
            glNormal3d(-n.x, -n.y, -n.z);
            glVertex3d(p0_.x, p0_.y, p0_.z);
            glVertex3d(p1_.x, p1_.y, p1_.z);
            glVertex3d(p2_.x, p2_.y, p2_.z);
            glVertex3d(p3_.x, p3_.y, p3_.z);
        glEnd();
    }


    glColor4f(1.0, 1.0, 0.2, 0.4);
    glBegin(GL_QUADS);
        glNormal3d(-n.x, -n.y, -n.z);
        glVertex3d(p0.x, p0.y, p0.z);
        glVertex3d(p1.x, p1.y, p1.z);
        glVertex3d(p2.x, p2.y, p2.z);
        glVertex3d(p3.x, p3.y, p3.z);
    glEnd();

    n = (p0 - eye).crossProduct(p1 - eye).normalize();
    glBegin(GL_TRIANGLES);
        glNormal3d(-n.x, -n.y, -n.z);
        glVertex3d(0,0,0);
        glVertex3d(p1.x, p1.y, p1.z);
        glVertex3d(p0.x, p0.y, p0.z);


        n = (p1 - eye).crossProduct(p2 - eye).normalize();

        glNormal3d(-n.x, -n.y, -n.z);
        glVertex3d(0,0,0);
        glVertex3d(p2.x, p2.y, p2.z);
        glVertex3d(p1.x, p1.y, p1.z);


        n = (p2 - eye).crossProduct(p3 - eye).normalize();

        glNormal3d(-n.x, -n.y, -n.z);
        glVertex3d(0,0,0);
        glVertex3d(p3.x, p3.y, p3.z);
        glVertex3d(p2.x, p2.y, p2.z);


        n = (p3 - eye).crossProduct(p0 - eye).normalize();

        glNormal3d(-n.x, -n.y, -n.z);
        glVertex3d(0,0,0);
        glVertex3d(p0.x, p0.y, p0.z);
        glVertex3d(p3.x, p3.y, p3.z);
    glEnd();

    glDisable(GL_COLOR_MATERIAL);

    glDepthMask(GL_TRUE);

    glPopMatrix();
}

//Vector3D Camera::getGlobalDirection(unsigned int _i, unsigned int _j, unsigned int _k, unsigned int _l) const
//{
//    double us = left + (right - left) * ((_i + 0.5)/n);
//    double vs = bottom + (top - bottom) * ((_j + 0.5)/m);
//
////    Vector3D s(us, vs, f);
////    return (transform.getLocalToWorld() * s.toHomogeneousCoordinates()).fromHomogeneousCoordinates();
//
//    Vector3D s = (u * us) + (v * vs) + (w * f);
//
//    return s; //
//}
